package impl

import java.time.LocalDateTime

import akka.actor.typed.{ActorRef, Behavior}
import akka.cluster.sharding.typed.scaladsl.{EntityContext, EntityTypeKey}
import akka.persistence.typed.PersistenceId
import akka.persistence.typed.scaladsl.{Effect, EventSourcedBehavior, ReplyEffect}
import com.lightbend.lagom.scaladsl.persistence.{AggregateEvent, AggregateEventTag, AggregateEventTagger, AkkaTaggerAdapter}
import com.lightbend.lagom.scaladsl.playjson.{JsonSerializer, JsonSerializerRegistry}
import play.api.libs.json.{Format, JsResult, JsSuccess, JsValue, Json, Reads, Writes}

import scala.collection.immutable

/**
 * Created by yz on 17/4/2020
 */
class HelloWorldAggregate {

}

object HelloWorldBehavior {

  def create(entityContext: EntityContext[HelloWorldCommand]):Behavior[HelloWorldCommand] = {

    val persistenceId = PersistenceId(entityContext.entityTypeKey.name, entityContext.entityId)

    create(persistenceId).withTagger(AkkaTaggerAdapter.fromLagom(entityContext, HelloWorldEvent.Tag))

  }

  private[impl] def create(persistenceId: PersistenceId) =
    EventSourcedBehavior.withEnforcedReplies[HelloWorldCommand, HelloWorldEvent, HelloWorldState](
      persistenceId = persistenceId, emptyState = HelloWorldState.initial,
      commandHandler = (cart, cmd) => cart.applyCommand(cmd),
      eventHandler = (cart, evt) => cart.applyEvent(evt)
    )


}

case class HelloWorldState(message: String, timestamp: String) {

  def applyCommand(cmd: HelloWorldCommand): ReplyEffect[HelloWorldEvent, HelloWorldState] = cmd match {
    case x: Hello => onHello(x)
    case x: UseGreetingMessage => onGreetingMessageUpgrade(x)
  }

  private def onHello(cmd: Hello): ReplyEffect[HelloWorldEvent, HelloWorldState] = Effect.reply(cmd.replyTo)(
    Greeting(s"$message,${cmd.name}"))

  private def onGreetingMessageUpgrade(cmd: UseGreetingMessage): ReplyEffect[HelloWorldEvent, HelloWorldState] = Effect.
    persist(GreetingMessageChanged(cmd.message)).thenReply(cmd.replyTo) { _ =>
    Accepted
  }

  def applyEvent(evt: HelloWorldEvent) = evt match {
    case GreetingMessageChange(message) => updateMessage(message)
  }

  private def updateMessage(newMessage: String) = copy(newMessage, LocalDateTime.now().toString)

}

object HelloWorldState {

  def initial: HelloWorldState = HelloWorldState("Hello", LocalDateTime.now.toString)

  val typeKey = EntityTypeKey[HelloWorldCommand]("HelloWorldAggregate")

  implicit val format: Format[HelloWorldState] = Json.format

}

sealed trait HelloWorldEvent extends AggregateEvent[HelloWorldEvent] {

  override def aggregateTag: AggregateEventTagger[HelloWorldEvent] = HelloWorldEvent.Tag

}

object HelloWorldEvent {
  val Tag = AggregateEventTag[HelloWorldEvent]
}

case class GreetingMessageChange(message: String) extends HelloWorldEvent


trait HelloWorldCommandSerializable

sealed trait HelloWorldCommand extends HelloWorldCommandSerializable

case class UseGreetingMessage(message: String, replyTo: ActorRef[Confirmation]) extends HelloWorldCommand

case class Hello(name: String, replyTo: ActorRef[Greeting]) extends HelloWorldCommand

final case class Greeting(message: String)

object Greeting {

  implicit val format: Format[Greeting] = Json.format

}

sealed trait Confirmation

case object Confirmation {
  implicit val format: Format[Confirmation] = new Format[Confirmation] {
    override def writes(o: Confirmation): JsValue = {
      o match {
        case acc: Accepted => Json.toJson(acc)
        case rej: Rejected => Json.toJson(rej)
      }
    }

    override def reads(json: JsValue): JsResult[Confirmation] = {
      if ((json \ "reason").isDefined) {
        Json.fromJson[Rejected](json)
      } else {
        Json.fromJson[Accepted](json)
      }
    }
  }
}

sealed trait Accepted extends Confirmation

case object Accepted extends Accepted {

  implicit val format: Format[Accepted] = Format(Reads(_ => JsSuccess(Accepted)), Writes(_ => Json.obj()))

}

case class GreetingMessageChanged(message: String) extends HelloWorldEvent

object GreetingMessageChanged {

  implicit val format: Format[GreetingMessageChanged] = Json.format

}

object HelloWorldSerializerRegistry extends JsonSerializerRegistry {
  override def serializers: immutable.Seq[JsonSerializer[_]] = immutable.Seq(
    JsonSerializer[GreetingMessageChanged],
    JsonSerializer[HelloWorldState],
    JsonSerializer[Greeting],
    JsonSerializer[Confirmation],
    JsonSerializer[Accepted],
    JsonSerializer[Rejected]
  )
}

case class Rejected(reason: String) extends Confirmation

object Rejected {
  implicit val format: Format[Rejected] = Json.format
}
